#include "vm/frame.h"
#include "threads/palloc.h"
#include "threads/malloc.h"
#include "threads/thread.h"
#include "threads/synch.h"
#include <list.h>

/* Frame table entry structure. */
struct frame_table_entry {
    void *kpage;                  // Physical frame address.
    void *upage;                  // User virtual page.
    struct thread *owner;         // Owner thread.
    struct list_elem elem;        // List element for the frame table.
};

static struct list frame_table;  // Global frame table.
static struct lock frame_lock;   // Lock for synchronizing frame table access.

/* Initializes the frame table. */
void frame_table_init(void) {
    list_init(&frame_table);
    lock_init(&frame_lock);
}

/* Allocates a frame for a user page. */
void *frame_alloc(void *upage) {
    lock_acquire(&frame_lock);

    void *kpage = palloc_get_page(PAL_USER);
    if (kpage == NULL) {
        // If no free frame is available, evict a frame.
        kpage = frame_evict();
    }

    struct frame_table_entry *fte = malloc(sizeof(struct frame_table_entry));
    if (fte == NULL) {
        lock_release(&frame_lock);
        return NULL;
    }

    fte->kpage = kpage;
    fte->upage = upage;
    fte->owner = thread_current();
    list_push_back(&frame_table, &fte->elem);

    lock_release(&frame_lock);
    return kpage;
}

/* Frees a frame. */
void frame_free(void *kpage) {
    lock_acquire(&frame_lock);

    struct list_elem *e;
    for (e = list_begin(&frame_table); e != list_end(&frame_table); e = list_next(e)) {
        struct frame_table_entry *fte = list_entry(e, struct frame_table_entry, elem);
        if (fte->kpage == kpage) {
            list_remove(&fte->elem);
            free(fte);
            palloc_free_page(kpage);
            break;
        }
    }

    lock_release(&frame_lock);
}

/* Evicts a frame using a basic FIFO strategy. */
void *frame_evict(void) {
    struct frame_table_entry *fte = list_entry(list_pop_front(&frame_table), struct frame_table_entry, elem);

    // Write the frame's content to swap if necessary.
    swap_out(fte->kpage);

    void *kpage = fte->kpage;
    free(fte);
    return kpage;
}
